<?php

namespace framework\core;

use Closure;
use Throwable;
use framework\core\Exception;
use framework\core\exception\Handle;

/**
 * Description of Middleware
 *
 * @author Administrator
 */
class Middleware {

    /**
     * 中间件执行队列
     * @var array
     */
    protected $queue = [];

    /**
     * 容器中的对象实例
     * @var array
     */
    protected $instances = [];

    public function __construct() {
        
    }

    public static function getInstance() {
        static $obj;
        if (!$obj) {
            $obj = new self();
        }
        return $obj;
    }

    /**
     * 导入中间件
     * @access public
     * @param array  $middlewares
     * @param string $type 中间件类型
     * @return void
     */
    public function import(array $middlewares = [], string $type = 'global'): void {
        foreach ($middlewares as $middleware) {
            $this->add($middleware, $type);
        }
    }

    /**
     * 注册中间件
     * @access public
     * @param mixed  $middleware
     * @param string $type 中间件类型
     * @return void
     */
    public function add(string $middleware, string $type = 'global'): void {

        $middleware = $this->buildMiddleware($middleware, $type);

        if (!empty($middleware)) {
            $this->queue[$type][] = $middleware;
            $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR);
        }
    }

    /**
     * 获取注册的中间件
     * @access public
     * @param string $type 中间件类型
     * @return array
     */
    public function all(string $type = 'global'): array {
        return $this->queue[$type] ?? [];
    }

    /**
     * 绑定参数
     * @access protected
     * @param ReflectionFunctionAbstract $reflect 反射类
     * @param array                      $vars    参数
     * @return array
     */
    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array {
        if ($reflect->getNumberOfParameters() == 0) {
            return [];
        }

        // 判断数组类型 数字数组时按顺序绑定参数
        reset($vars);
        $type = key($vars) === 0 ? 1 : 0;
        $params = $reflect->getParameters();
        $args = [];

        foreach ($params as $param) {
            $name = $param->getName();
            $lowerName = $name;
            $reflectionType = $param->getType();

            if ($param->isVariadic()) {
                return array_merge($args, array_values($vars));
            } elseif ($reflectionType && $reflectionType instanceof \ReflectionNamedType && $reflectionType->isBuiltin() === false) {
                $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
            } elseif (1 == $type && !empty($vars)) {
                $args[] = array_shift($vars);
            } elseif (0 == $type && array_key_exists($name, $vars)) {
                $args[] = $vars[$name];
            } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
                $args[] = $vars[$lowerName];
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } else {
                throw new \InvalidArgumentException('method param miss:' . $name);
            }
        }

        return $args;
    }

    /**
     * 调用反射执行类的实例化 支持依赖注入
     * @access public
     * @param string $class 类名
     * @param array  $vars  参数
     * @return mixed
     */
    public function invokeClass(string $class, array $vars = []) {
        try {
            $reflect = new \ReflectionClass($class);
        } catch (ReflectionException $e) {
            throw new Exception('class not exists: ' . $class, $class, $e);
        }

        $constructor = $reflect->getConstructor();

        $args = $constructor ? $this->bindParams($constructor, $vars) : [];

        $object = $reflect->newInstanceArgs($args);

        return $object;
    }

    /**
     * 创建类的实例 已经存在则直接获取
     * @template T
     * @param string|class-string<T> $abstract    类名或者标识
     * @param array                  $vars        变量
     * @param bool                   $newInstance 是否每次创建新的实例
     * @return T|object
     */
    public function make(string $abstract, array $vars = [], bool $newInstance = false) {

        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }

        $object = $this->invokeClass($abstract, $vars);

        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }

    /**
     * 调度管道
     * @access public
     * @param string $type 中间件类型
     * @return Pipeline
     */
    public function pipeline(string $type = 'global') {
        return (new Pipeline())
                        ->through(array_map(function ($middleware) {
                                    return function ($request, $next) use ($middleware) {
                                        [$call, $params] = $middleware;
                                        if (is_array($call) && is_string($call[0])) {
                                            $call = [$this->make($call[0]), $call[1]];
                                        }

                                        $response = call_user_func($call, $request, $next, ...$params);

                                        return $response;
                                    };
                                }, $this->all($type)))
                        ->whenException([$this, 'handleException']);
    }

    /**
     * 结束调度
     */
    public function end() {
        foreach ($this->queue as $queue) {
            foreach ($queue as $middleware) {
                [$call] = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $instance = $this->make($call[0]);
                    if (method_exists($instance, 'end')) {
                        $instance->end();
                    }
                }
            }
        }
    }

    /**
     * 异常处理
     * @param Throwable $e
     * @return Response
     */
    public function handleException(Throwable $e) {
        /** @var Handle $handler */
        $handler = new Handle();

        $handler->report($e);

        return $handler->render($e);
    }

    /**
     * 解析中间件
     * @access protected
     * @param array|string|Closure  $middleware
     * @param string $type 中间件类型
     * @return array
     */
    protected function buildMiddleware($middleware, string $type): array {
        if (is_array($middleware)) {
            [$middleware, $params] = $middleware;
        }

        if ($middleware instanceof Closure) {
            return [$middleware, $params ?? []];
        }

        if (is_array($middleware)) {
            $this->import($middleware, $type);
            return [];
        }

        return [[$middleware, 'handle'], $params ?? []];
    }

}
